package com.shinnawy.ispeaka;

import java.util.Calendar;

import android.app.Service;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;

public class iSpeakaHardwareManager extends PhoneStateListener implements SensorEventListener {
	private static iSpeakaHardwareManager mInstance;
	public static iSpeakaHardwareManager getInstance() {
		if(mInstance == null)
			mInstance = new iSpeakaHardwareManager();
		return mInstance;
	}

	private iSpeakaHardwareManager() {
		Logger.Log(this, "iSpeakaHardwareManager");
	}

	public void start() {
		Logger.Log(this, "start");
		if(iSpeakaWidget.WIDGET_CONTEXT == null)
		{
			Logger.Log(this, "Widget is not active, return");
			return;
		}
		mAM = (AudioManager)iSpeakaWidget.WIDGET_CONTEXT.getSystemService(Service.AUDIO_SERVICE);
		mSM = (SensorManager)iSpeakaWidget.WIDGET_CONTEXT.getSystemService(Service.SENSOR_SERVICE);
		mTM = (TelephonyManager)iSpeakaWidget.WIDGET_CONTEXT.getSystemService(Service.TELEPHONY_SERVICE);
		if(mTM == null)
		{
			Logger.Log(this, "mTM = null, return");
			return;
		}
		mTM.listen(this, PhoneStateListener.LISTEN_CALL_STATE);
	}

	public void stop() {
		Logger.Log(this, "stop");
		if(mSM == null || mTM == null)
		{
			Logger.Log(this, "mSM or mTM = null, return");
			return;
		}
		mSM.unregisterListener(this);
		mTM.listen(this, PhoneStateListener.LISTEN_NONE);
		mSampleCounter = 0;
		mIsInPocket = null;
	}

	@Override
	public void onCallStateChanged(int state, String incomingNumber) {
		Logger.Log(this, "onCallStateChanged: state (" + state + ") incomingNumber (" + incomingNumber + ")");
		if(mAM == null || mSM == null)
		{
			Logger.Log(this, "mAM or mSM = null, return");
			return;
		}
		switch (state) {
		case TelephonyManager.CALL_STATE_IDLE:
			Logger.Log(this, "onCallStateChanged (CALL_STATE_IDLE)");
			if(mCanChangeRingerMode)
				mAM.setRingerMode(mOldRingerMode);
			mCanChangeRingerMode = false;
			mIsInPocket = null;
			mSampleCounter = 0;
			mSM.unregisterListener(this);
			mInACall = false;
			mIsCallWaiting = false;
			break;
		case TelephonyManager.CALL_STATE_RINGING:
			Logger.Log(this, "onCallStateChanged (CALL_STATE_RINGING)");
			if(mInACall)
				mIsCallWaiting = true;
			if(!mIsCallWaiting) {
				mCanChangeRingerMode = true;
				mOldRingerMode = mAM.getRingerMode();
				mSM.registerListener(this, mSM.getSensorList(Sensor.TYPE_PROXIMITY).get(0), SensorManager.SENSOR_DELAY_FASTEST);
				mSM.registerListener(this, mSM.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0), SensorManager.SENSOR_DELAY_FASTEST);
			}
			break;
		case TelephonyManager.CALL_STATE_OFFHOOK:
			Logger.Log(this, "onCallStateChanged (CALL_STATE_OFFHOOK)");
			mSM.unregisterListener(this);
			mSM.registerListener(this, mSM.getSensorList(Sensor.TYPE_PROXIMITY).get(0), SensorManager.SENSOR_DELAY_FASTEST);
			if(mCanChangeRingerMode)
				mAM.setRingerMode(mOldRingerMode);
			mCanChangeRingerMode = false;
			mIsInPocket = null;
			mSampleCounter = 0;
			mInACall = true;
			break;

		default:
			break;
		}
	}

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	}

	public void onSensorChanged(SensorEvent event) {
		Logger.Log(this, "onSensorChanged");
		if(event == null || mAM == null)
		{
			Logger.Log(this, "event or mAM = null, return");
			return;
		}
		if(event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
			if(!mIsCallWaiting) {
				Logger.Log(this, "sensor = (TYPE_PROXIMITY) valueCount = (" + event.values.length + ")");
				if(mCanChangeRingerMode && event.values != null && mSampleCounter < mTargetSampleCount) {
					if(mIsInPocket == null) {
						mIsInPocket = new SensorSample[mTargetSampleCount];
					}
					SensorSample ss = new SensorSample();
					mIsInPocket[mSampleCounter++] = ss;
					ss.Flag = event.values[0] < .5f;
					ss.TimeStamp = Calendar.getInstance();
					Logger.Log(this, "mIsInPocket[" + String.valueOf(mSampleCounter-1) +"] = " + ss.toString());
					return;
				}

				if(mCanChangeRingerMode) return;
			}

			//If wired headset or bluetooth headset is on, don't change the speaker status
			if(mAM.isWiredHeadsetOn() || mAM.isBluetoothA2dpOn()) return;

			boolean near = false;
			if(event.values != null) {
				near = event.values[0] < .5f;
			}

			if(near)
			{
				//Speaker off
				if(mAM != null)
				{
					mAM.setSpeakerphoneOn(false);
					mAM.setMode(AudioManager.MODE_IN_CALL);
				}
			}
			else
			{
				//Speaker on
				if(mAM != null)
				{
					mAM.setSpeakerphoneOn(true);
					mAM.setMode(AudioManager.MODE_IN_CALL);
				}
			}
		}
		else if(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
			boolean makeSilent = false;
			if(event.values != null) {
				Logger.Log(this, "sensor = (TYPE_ACCELEROMETER) valueCount = (" + event.values.length + ")");
				Logger.Log(this, "values[2] = " + String.valueOf(event.values[2]));
				makeSilent = event.values[2] < -9.70f;
			}

			if(makeSilent && mCanChangeRingerMode && !isInPocket()) {
				mAM.setRingerMode(AudioManager.RINGER_MODE_SILENT);
			}
		}
	}

	private boolean isInPocket() {
		Logger.Log(this, "isInPocket");
		if(mIsInPocket == null || mIsInPocket.length < mTargetSampleCount) {
			Logger.Log(this, "return 1");
			return true;
		}
		for(int i = mIsInPocket.length-1; i >= 0; i--) {
			if(mIsInPocket[i] == null) {
				Logger.Log(this, "return 2");
				return true;
			}
		}
		long diff = mIsInPocket[1].TimeStamp.getTimeInMillis() - mIsInPocket[0].TimeStamp.getTimeInMillis();
		Logger.Log(this, "Timestamp diff = " + diff);
		if(mIsInPocket[0].Flag == false && mIsInPocket[1].Flag == true && diff > 200) return false;
		else { 
			mIsInPocket = null;
			mSampleCounter = 0;
		}
		return true;
	}

	private SensorManager mSM = null;
	private AudioManager mAM = null;
	private TelephonyManager mTM = null;
	private int mOldRingerMode;
	private boolean mCanChangeRingerMode;
	private SensorSample[] mIsInPocket;
	private int mSampleCounter;
	private final static int mTargetSampleCount = 2;
	private boolean mInACall;
	private boolean mIsCallWaiting;
	private class SensorSample {
		public boolean Flag;
		public Calendar TimeStamp;

		@Override
		public String toString() {
			if(TimeStamp == null)
			{
				Logger.Log(this, "TimeStamp = null, return original super.toString()");
				return super.toString();
			}
			return "SensorSample: Flag = " + String.valueOf(Flag) + ", Timestamp = " + TimeStamp.toString();
		}
	}
}